Back MessageList with NSTextView (re-target of #2 to main)#3
Merged
Conversation
SwiftUI's .textSelection(.enabled) only permits selection within a
single Text view — drag-selecting across chat messages was impossible.
Replace the LazyVStack { MessageRow } rendering in MessageList and
ServerMessageList with a new MessageBufferView: NSViewRepresentable
wrapping NSScrollView + a non-editable NSTextView.
What you now get for free from AppKit:
- True drag-selection across any contiguous range of the buffer, across
rows and message kinds.
- Cmd+A to select the whole buffer.
- Native Cmd+C copy of the selection as plain text.
- Native incremental Find via Cmd+F (in addition to the existing
SwiftUI FindBar, which continues to work as a channel filter).
- Right-click context menu with Look Up / Search / Share plus our
Copy Message / Copy Text / Copy Nickname items.
Rendering in the coordinator mirrors the prior SwiftUI output:
timestamp + sender + IRC-styled content, highlight lines get an accent
background, and a centered "── new ──" paragraph marks the first
unread message. Nick colors round-trip to NSColor via NSColor(Color).
MessageBufferView diff-appends on pure tail extensions and does a full
rebuild when options change (nick-colors toggle, timestamp format,
last-read marker, or message reordering from find-filter). Scroll
pinning follows the classic mIRC rule: if the user was at the bottom
before append, stay at the bottom; otherwise leave their position
alone so they can read history.
The dead SwiftUI views MessageRow, LineMarker, LinkPreviewView, and
the firstPreviewableURL/_linkDetector helpers are removed. Link
previews are not rendered in this build; the existing "Fetch link
previews" preference toggle has no effect until a follow-up restores
inline rendering via NSTextAttachment.
Implementation notes:
- Coordinator is @mainactor; textView and scrollView are weak refs.
- `@objc private func copyAction(_:)` is the one ObjC-adjacent line
required to satisfy NSMenuItem's target-action contract. It is a
private selector callback, not a new public API surface.
- `.bryggaMessageID` attributed-string key tags each message's
paragraph range so the menu handler can recover the clicked Message.
Add a LinkPreviewAttachmentCell subclass of NSTextAttachmentCell that draws the familiar rounded card — thumbnail on the left, site name / title / summary on the right — directly inside the NSTextView buffer. For direct-image URLs (Content-Type image/*) the cell fills the card with the image itself, bounded at 420×240. The card is indented under the message body (firstLineHeadIndent 68) so it lines up beneath the content rather than under the timestamp column. Observation flow: - MessageBufferView takes a LinkPreviewStore? and a linkPreviewsEnabled flag. MessageList and ServerMessageList thread these through from AppState and the existing @AppStorage toggle respectively — the preference toggle works again. - The Coordinator observes `store.cache` via withObservationTracking. When any preview status changes the onChange fires on the main actor, the coordinator reapplies from its cached last inputs, and re-subscribes. - Image bytes are decoded once per Coordinator lifetime into an in-memory NSImage cache keyed by URL. Fetch uses a per-request URLSession.ephemeral with a 10 s timeout and 2 MB cap, matching LinkPreviewStore's own bounds. When an image lands the coordinator reapplies and the cell is redrawn. Clicking anywhere on the preview card follows the URL — the attachment character carries a .link attribute pointing at the preview's source URL. LinkPreviewAttachmentCell is deliberately not @mainactor because NSCell's overridable methods are declared nonisolated in AppKit. The class holds only immutable value-type state (LinkPreview + NSImage?) so nonisolated access is safe. Its layout constants are nonisolated static lets so they can be referenced from the draw path without a main-actor hop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Re-lands #2 against
main. #2 was stacked onselectable-chat-textas its base; merging it landed on the stack's base branch, not on main, so main never got theNSTextViewbuffer and cross-row drag-selection is still broken there today.This PR is exactly the two commits from #2 cherry-picked onto current
main:64b352cBack MessageList with NSTextView for cross-row selectiona3aee7dRender inline link previews as NSTextAttachment cellsAlready reviewed in #2 — full rationale, test plan, and screenshots live on that PR. Nothing new here except the target.
Test plan
swift build— passesswift test— 88 tests pass (unchanged)swiftformat --lint .— cleanScripts/build-app.sh— buildsbuild/Brygga.appfineRisk / rollback
Same as #2. Revert the two commits to return to main's current
LazyVStack-backed buffer.